package com.mop.core.es.dao.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.lemon.core.common.lang.util.*;
import com.mop.core.es.api.annotation.ESField;
import com.mop.core.es.api.annotation.IndexName;
import com.mop.core.es.dao.constant.ESConfig;
import com.mop.core.es.dao.dao.impl.ElasticSearchDao;
import com.mop.core.es.dao.dao.impl.ElasticSearchDaoImpl;
import com.mop.core.es.dao.plugins.Pagination;
import com.mop.core.es.dao.plugins.aggs.Statistics;
import com.mop.core.es.dao.plugins.wraper.QueryWraper;
import io.searchbox.client.JestClient;
import io.searchbox.client.JestResult;
import io.searchbox.core.Search;
import io.searchbox.core.SearchResult;
import io.searchbox.core.SearchScroll;
import io.searchbox.core.search.aggregation.*;
import io.searchbox.core.search.sort.Sort;
import io.searchbox.params.Parameters;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregatorFactories;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;

/**
 * @author lemon
 * @ClassName ESHelper
 * @desc es转换工具类
 * @Date 2018/12/17 11:26
 * @Version 1.0
 **/
public class ESHelper {

    private static JestClient jestClient = ElasticSearchDaoImpl.getJestClient();

    public static String getIndexName(Class<?> clazz) {
        IndexName annotation = getIndexNameAnno(clazz);
        String indexName = annotation.index();
        Assert.hasLength(indexName, "index不可为空");
        return indexName;
    }

    public static <T> String getIndexName(QueryWraper<T> queryWraper) {
        return getIndexName(queryWraper.getEntity().getClass());
    }

    public static String getTypeName(Class<?> clazz) {
        IndexName annotation = getIndexNameAnno(clazz);
        String typeName = annotation.type();
        Assert.hasLength(typeName, "type");
        return typeName;
    }

    public static <T> String getTypeName(QueryWraper<T> queryWraper) {
        return getTypeName(queryWraper.getEntity().getClass());
    }

    private static IndexName getIndexNameAnno(Class<?> clazz) {
        IndexName annotation = clazz.getAnnotation(IndexName.class);
        Assert.notNull(annotation, "请使用@IndexName注解实体类");
        return annotation;
    }

    public static <T> String getDocId(T t) {
        String docId = "";
        Object docIdObj = ReflectionUtils.getFieldValue(t, "docId");
        if (!ObjectUtils.isEmpty(docIdObj)) {
            docId = String.valueOf(docIdObj);
        }
        Assert.hasLength(docId, "docId不可为空!");
        return docId;
    }

    public static boolean isSucceeded(JestResult jestResult) {
        boolean result = false;
        if (jestResult != null && jestResult.isSucceeded()) {
            result = true;
        }
        return result;
    }

    public static <T> JSONObject getInsertEntity(T entity) {
        JSONObject instance = new JSONObject();
        List<Field> fields = ReflectionUtils.getDeclaredFields(entity.getClass());
        for (Field field : fields) {
            if ("docId".equals(field.getName())) {
                continue;
            }
            Object fieldValue = ReflectionUtils.getFieldValue(entity, field);
            if (ObjectUtils.isEmpty(fieldValue)) {
                continue;
            }
            ESField[] annotationsByType = field.getAnnotationsByType(ESField.class);
            String column = field.getName();
            if (!ObjectUtils.isEmpty(annotationsByType)) {
                column = annotationsByType[0].column();
            }

            instance.put(column, fieldValue);
        }
        return instance;
    }

    /*public static <T> T getInsertEntity(T entity) {
        try {
            Object instance = entity.getClass().newInstance();
            List<Field> fields = ReflectionUtils.getDeclaredFields(entity.getClass());
            for (Field field : fields) {
                if ("docId".equals(field.getName())) {
                    continue;
                }
                Object fieldValue = ReflectionUtils.getFieldValue(entity, field);
                if (ObjectUtils.isEmpty(fieldValue)) {
                    continue;
                }
                ReflectionUtils.setFieldValue(instance, field, fieldValue, false);
            }
            return (T) instance;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }*/

    public static <T> T getUpdateEntity(T oldEntity, T newEntity) {
        // 拿newT的非空值去替换oldT的值
        List<Field> fields = ReflectionUtils.getDeclaredFields(newEntity.getClass());
        for (Field field : fields) {
            if ("docId".equals(field.getName())) {
                continue;
            }
            Object fieldValue = ReflectionUtils.getFieldValue(newEntity, field);
            if (ObjectUtils.isEmpty(fieldValue)) {
                continue;
            }
            ReflectionUtils.setFieldValue(oldEntity, field, fieldValue, false);
        }
        return oldEntity;
    }

    public static SearchSourceBuilder getPageSearchSourceBuilder(Pagination page) {
        SearchSourceBuilder searchSourceBuilder = getSearchSourceBuilder();
        /*if (page.getOrderByFields() != null && page.getOrderByFields().size() > 0) {
            for (String orderByField : page.getOrderByFields()) {
                String[] items = orderByField.split(" ");
                if (items != null && items.length == 2) {
                    String field = items[0];
                    String order = items[1];
                    SortOrder sortOrder = SortOrder.ASC;
                    if ("desc".equals(order.toLowerCase())) {
                        sortOrder = SortOrder.DESC;
                    }
                    searchSourceBuilder.sort(field, sortOrder);
                }
            }
        }

        if (page.getAscs() != null && page.getAscs().size() > 0) {
            for (String field : page.getAscs()) {
                searchSourceBuilder.sort(field, SortOrder.ASC);
            }
        }

        if (page.getDescs() != null && page.getDescs().size() > 0) {
            for (String field : page.getDescs()) {
                searchSourceBuilder.sort(field, SortOrder.DESC);
            }
        }*/
        return searchSourceBuilder;
    }

    public static SearchSourceBuilder getSearchSourceBuilder() {
        return new SearchSourceBuilder().size(ESConfig.SIZE);
    }

    public static <T> List<Statistics> selectAggs(QueryWraper<T> queryWraper) {
        String index = getIndexName(queryWraper);
        String type = getTypeName(queryWraper);
        return selectAggs(index, type, queryWraper);
    }

    public static <T> List<Statistics> selectAggs(String index, String type, QueryWraper<T> queryWraper) {
        List<Statistics> statistics = new LinkedList<Statistics>();
        queryWraper.setIsAggs(true);
        // 查询获取结果集，然后解析聚合结果
        SearchResult searchResult = getSearchResult(index, type, queryWraper);
        MetricAggregation metricAggregation = searchResult.getAggregations();
        // 判断取什么聚合结果
        AggregatorFactories.Builder aggregations = queryWraper.getSearchSourceBuilder().aggregations();
        if (ObjectUtils.isEmpty(aggregations)) {
            return statistics;
        }
        List<AggregationBuilder> aggregatorFactories = aggregations.getAggregatorFactories();
        for (AggregationBuilder aggregatorFactory : aggregatorFactories) {
            Statistics statis = new Statistics();
            String aggName = aggregatorFactory.getName();
            JSONObject maxObj = JSON.parseObject(aggregatorFactory.toString());
            String fieldName = "";
            if (aggName.contains("max")) {
                MaxAggregation maxAggregation = metricAggregation.getMaxAggregation(aggName);
                fieldName = maxObj.getJSONObject(aggName).getJSONObject("max").getString("field");
                statis.setValue(maxAggregation.getMax());
                statis.setMetrics(fieldName);
                statistics.add(statis);
            } else if (aggName.contains("min")) {
                MinAggregation minAggregation = metricAggregation.getMinAggregation(aggName);
                fieldName = maxObj.getJSONObject(aggName).getJSONObject("min").getString("field");
                statis.setValue(minAggregation.getMin());
                statis.setMetrics(fieldName);
                statistics.add(statis);
            } else if (aggName.contains("sum")) {
                SumAggregation sumAggregation = metricAggregation.getSumAggregation(aggName);
                fieldName = maxObj.getJSONObject(aggName).getJSONObject("sum").getString("field");
                statis.setValue(sumAggregation.getSum());
                statis.setMetrics(fieldName);
                statistics.add(statis);
            } else if (aggName.contains("count")) {
                ValueCountAggregation valueCountAggregation = metricAggregation.getValueCountAggregation(aggName);
                fieldName = maxObj.getJSONObject(aggName).getJSONObject("count").getString("field");
                statis.setCount(valueCountAggregation.getValueCount());
                statis.setMetrics(fieldName);
                statistics.add(statis);
            } else if (aggName.contains("avg")) {
                AvgAggregation avgAggregation = metricAggregation.getAvgAggregation(aggName);
                fieldName = maxObj.getJSONObject(aggName).getJSONObject("avg").getString("field");
                statis.setValue(avgAggregation.getAvg());
                statis.setMetrics(fieldName);
                statistics.add(statis);
            } else if (aggName.contains("terms")) {
                TermsAggregation termsAggregation = metricAggregation.getTermsAggregation(aggName);
                //fieldName = maxObj.getJSONObject(aggName).getJSONObject("terms").getString("field");
                List<TermsAggregation.Entry> buckets = termsAggregation.getBuckets();
                for (TermsAggregation.Entry bucket : buckets) {
                    statis = new Statistics();
                    fieldName = bucket.getKey();
                    Long count = bucket.getCount();
                    statis.setCount(count);
                    statis.setMetrics(fieldName);
                    statistics.add(statis);
                }
            }
        }
        return statistics;
    }

    public static <T> List<T> selectRecords(QueryWraper<T> queryWraper) {
        String index = getIndexName(queryWraper);
        String type = getTypeName(queryWraper);
        return selectRecords(index, type, queryWraper);
    }

    public static <T> List<T> selectRecords(String index, String type, QueryWraper<T> queryWraper) {
        List<T> list = new LinkedList<T>();
        SearchResult searchResult = getSearchResult(index, type, queryWraper);
        try {
            list = getByScrollId(searchResult, (Class<T>) queryWraper.getEntity().getClass(), queryWraper.getStart(), queryWraper.getEnd());
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return list;
    }

    public static <T> SearchResult getSearchResult(String index, String type, QueryWraper<T> queryWraper) {
        if (StringUtils.isBlank(index)) {
            index = ESHelper.getIndexName(queryWraper.getEntity().getClass());
        }
        if (StringUtils.isBlank(type)) {
            type = ESHelper.getTypeName(queryWraper.getEntity().getClass());
        }
        checkParams(index, type);

        Search.Builder searchBuilder = new Search.Builder(queryWraper.getSearchSourceBuilder().toString())
                .addIndex(index)
                .addType(type);
        if (!queryWraper.isAggs()) {
            searchBuilder.setParameter(Parameters.SCROLL, ESConfig.SCROLL_ALIVE_TIME);
        }
        Search search = searchBuilder.build();
        try {
            return jestClient.execute(search);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static <T> List<T> getByScrollId(SearchResult searchResult, Class<T> clazz, Integer start, Integer end) throws InstantiationException, IllegalAccessException {
        if (searchResult.getResponseCode() == 400) {
            JsonElement error = searchResult.getJsonObject().get("error");
            System.err.println(error.toString());
            return new LinkedList<T>();
        }
        JsonObject resultObj = searchResult.getJsonObject();
        String scrollId = resultObj.get("_scroll_id").getAsString();
        List<T> list = parseList(resultObj, clazz);
        // 当前命中结果数
        if (end == null) {
            end = getHitsTotal(resultObj);
        }
        if (start == null) {
            start = 1;
        }
        if (list.size() >= end) {
            return list.subList(start - 1, end);
        }
        List<T> scrollList = getByScrollId(scrollId, clazz, start, end);
        list.addAll(scrollList);
        return list;
    }

    public static <T> List<T> getByScrollId(String scrollId, Class<T> clazz, Integer start, Integer end) throws InstantiationException, IllegalAccessException {
        List<T> list = new LinkedList<T>();
        while (true) {
            //直接滚动
            SearchScroll scroll = new SearchScroll.Builder(scrollId, ESConfig.SCROLL_ALIVE_TIME).build();
            try {
                JestResult jestResult = jestClient.execute(scroll);
                // 第一次滚动时,判断scrollId是否过期,过期抛出异常
                JsonObject resultObj = jestResult.getJsonObject();
                JsonObject jsonObject = resultObj.getAsJsonObject("error");
                Boolean isSucceeded = jestResult.isSucceeded();
                if (jsonObject != null || !isSucceeded) {
                    break;
                }

                JsonArray hitsArray = resultObj.getAsJsonObject("hits").getAsJsonArray("hits");
                if (hitsArray.size() == 0) {
                    break;
                }

                List<T> items = parseList(resultObj, clazz);
                list.addAll(items);

                if (list.size() >= (end - ESConfig.SIZE)) {
                    return list.subList(start - 1, (end - ESConfig.SIZE));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return list;
    }

    public static <T> List<T> parseList(JsonObject jsonObject, Class<T> clazz)
            throws IllegalAccessException, InstantiationException {

        // 根据clazz解析成不同的, 如Map、JSONObject等
        if (clazz == Map.class || clazz == HashMap.class || clazz == LinkedHashMap.class) {
            return parseMapList(jsonObject, clazz);
        } else if (clazz == JSONObject.class) {
            return parseJSONObjectList(jsonObject, clazz);
        } else if (clazz == JsonObject.class) {
            return parseJsonObjectList(jsonObject, clazz);
        } else if (clazz.toString().contains("mop") || clazz.toString().contains("lemon")) {
            return parseJavaBeanList(jsonObject, clazz);
        } else {
            System.out.println("暂不支持当前类型解析.");
            return new LinkedList<T>();
        }
    }

    public static <T> List<T> parseJavaBeanList(JsonObject jsonObject, Class<T> clazz)
            throws IllegalAccessException, InstantiationException {
        List<T> entities = new LinkedList<T>();
        List<Field> fields = ReflectionUtils.getDeclaredFields(clazz);
        JsonObject wrapHits = jsonObject.getAsJsonObject("hits");
        int total = wrapHits.get("total").getAsInt();
        JsonArray hits = wrapHits.getAsJsonArray("hits");
        for (JsonElement element : hits) {
            T instance = clazz.newInstance();
            JsonObject elementObj = element.getAsJsonObject();
            String docId = elementObj.get("_id").getAsString();
            JsonObject sourceObj = elementObj.getAsJsonObject("_source");
            JsonObject highlight = elementObj.getAsJsonObject("highlight");

            for (Field field : fields) {
                if ("docId".equals(field.getName())) {
                    ReflectionUtils.setFieldValue(instance, "docId", docId);
                }

                // 使用高亮替换当前文本
                boolean isReplace = false;
                if (highlight != null && !highlight.isJsonNull()) {
                    for (Map.Entry<String, JsonElement> elementEntry : highlight.entrySet()) {
                        String key = elementEntry.getKey();
                        JsonElement value = elementEntry.getValue();
                        String asString = value.getAsString();
                        if (field.getName().equals(key)) {
                            String fieldVal = value.getAsString();
                            ReflectionUtils.setFieldValue(instance, field, fieldVal);
                            isReplace = true;
                        }
                    }
                }
                if (isReplace) {
                    continue;
                }

                String esColumnName;
                ESField esField = field.getAnnotation(ESField.class);
                if (!ObjectUtils.isEmpty(esField)) {
                    esColumnName = esField.column();
                } else {
                    esColumnName = field.getName();
                }

                JsonElement jsonElement = sourceObj.get(esColumnName);
                if (jsonElement != null) {
                    String fieldVal = "";
                    if (!jsonElement.isJsonArray()) {
                        fieldVal = jsonElement.getAsString();
                    } else if (jsonElement.getClass() == JsonArray.class){
                        fieldVal = jsonElement.getAsJsonArray().toString();
                    }
                    ReflectionUtils.setFieldValue(instance, field, fieldVal);
                }
            }
            entities.add(instance);
        }
        return entities;
    }

    public static <T> List<T> parseMapList(JsonObject jsonObject, Class<T> clazz) {
        List<T> entities = new LinkedList<T>();
        return entities;
    }

    public static <T> List<T> parseJSONObjectList(JsonObject jsonObject, Class<T> clazz) {
        List<T> entities = new LinkedList<T>();
        return entities;
    }

    public static <T> List<T> parseJsonObjectList(JsonObject jsonObject, Class<T> clazz) {
        List<T> entities = new LinkedList<T>();
        return entities;
    }

    public static <T> QueryBuilder getMapMustBoolBuilder(Map<String, Object> param) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        for (String key : param.keySet()) {
            Object objValue = param.get(key);
            if (ObjectUtils.isEmpty(objValue) || StringUtils.isBlank(objValue.toString())) {
                continue;
            }
            MatchQueryBuilder matchQuery = QueryBuilders.matchQuery(key, objValue.toString());
            boolQueryBuilder.must(matchQuery);
        }
        return boolQueryBuilder;
    }

    public static <T> void setClazzMustBoolBuilder(BoolQueryBuilder boolQueryBuilder, T entity) {
        List<String> fieldNames = ReflectionUtils.getDeclaredFieldNames(entity.getClass());
        for (String fieldName : fieldNames) {
            if ("docId".equals(fieldName)) {
                continue;
            }
            Object fieldValue = ReflectionUtils.getFieldValue(entity, fieldName);
            if (ObjectUtils.isEmpty(fieldValue) || StringUtils.isBlank(fieldValue.toString())) {
                continue;
            }
            MatchQueryBuilder fieldQuery = QueryBuilders.matchQuery(fieldName, fieldValue.toString());
            boolQueryBuilder.must(fieldQuery);
        }
    }

    private static Integer getHitsTotal(JsonObject jsonObject) {
        JsonObject wrapHits = jsonObject.getAsJsonObject("hits");
        return wrapHits.get("total").getAsInt();
    }

    public static void checkParams(String index, String type) {
        Assert.hasLength(index, "index不可为空!");
        Assert.hasLength(type, "type不可为空!");
    }
}
